
#include "Stdafx.h"
#include "InternalManager.h"

#define Release(x) { if (x) { x->Release(); x = NULL; } }

namespace GRIP
{
	namespace Cuda
	{
		HWND InternalManager::Window = NULL;
		IDirect3D9Ex* InternalManager::Direct3D = NULL;
		IDirect3DDevice9Ex* InternalManager::Direct3DDevice = NULL;
		
		int InternalManager::devID = -1;

		int InternalManager::FindBestDevice()
		{
			int sm_per_multiproc = 0,
				max_compute_perf = 0,
				max_perf_device = 0,
				device_count = 0,
				best_SM_arch = 0;

			cudaDeviceProp deviceProp;

			CudaErrorCheck(cudaGetDeviceCount( &device_count ));

			if(device_count < 1)
				throw gcnew NoCudaDevicesException();

			// Find the best major SM Architecture GPU device
			for(int i = 0; i < device_count; i++)
			{
				CudaErrorCheck(cudaGetDeviceProperties( &deviceProp, i ));
				if (deviceProp.major > 0 && deviceProp.major < 9999)
					best_SM_arch = best_SM_arch > deviceProp.major ? best_SM_arch : deviceProp.major;
			}

			// Find the fastest device with the best shader model
			for(int i = 0; i < device_count; i++)
			{
				CudaErrorCheck(cudaGetDeviceProperties( &deviceProp, i ));
				if (deviceProp.major == 9999 && deviceProp.minor == 9999)
					sm_per_multiproc = 1;
				else
					sm_per_multiproc = GetSMCoreCount(deviceProp.major, deviceProp.minor);

				int compute_perf = deviceProp.multiProcessorCount * sm_per_multiproc * deviceProp.clockRate;
				if( compute_perf > max_compute_perf ) {
					// If we find GPU with SM major > 2, search only these
					if ( best_SM_arch > 2 ) {
						// If our device==dest_SM_arch, choose this, or else pass
						if (deviceProp.major == best_SM_arch) {	
							max_compute_perf = compute_perf;
							max_perf_device = i;
						}
					} else {
						max_compute_perf = compute_perf;
						max_perf_device = i;
					}
				}
			}
			return max_perf_device;
		}

		inline int InternalManager::GetSMCoreCount(int major, int minor)
		{
			switch((major << 4) + minor)
			{
			case 0x10:
			case 0x11:
			case 0x12:
			case 0x13:
				return 8;
			case 0x20:
				return 32;
			case 0x21:
				return 48;
			case 0x30:
				return 192;
			default:
				throw gcnew UnsupportedComputeVersionException(major, minor);
			}
		}


		void InternalManager::Init()
		{
			devID = FindBestDevice();
			WNDCLASS wndclass;

			wndclass.style = CS_HREDRAW | CS_VREDRAW;
			wndclass.lpfnWndProc = DefWindowProc;
			wndclass.cbClsExtra = 0;
			wndclass.cbWndExtra = 0;
			wndclass.hInstance = NULL;
			wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
			wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
			wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
			wndclass.lpszMenuName = NULL;
			wndclass.lpszClassName = TEXT("InternalManager");

			RegisterClass(&wndclass);

			Window = CreateWindow(wndclass.lpszClassName, TEXT("InternalManager"), WS_OVERLAPPEDWINDOW,
				0, 0, 0, 0, NULL, NULL, NULL, NULL);
		
			DirectXErrorCheck(Direct3DCreate9Ex(D3D_SDK_VERSION, &Direct3D),
				"Failed to initialize Direct3D9Ex.");

			D3DPRESENT_PARAMETERS d3dParams;
			ZeroMemory(&d3dParams, sizeof(d3dParams));
			d3dParams.Windowed = TRUE;
			d3dParams.BackBufferFormat = D3DFMT_UNKNOWN;
			d3dParams.BackBufferHeight = 1;
			d3dParams.BackBufferWidth = 1;
			d3dParams.SwapEffect = D3DSWAPEFFECT_DISCARD;

			D3DCAPS9 caps;
			DirectXErrorCheck(Direct3D->GetDeviceCaps(devID, D3DDEVTYPE_HAL, &caps),
				"Failed to get DirectX device capabilities.");

			DWORD vertexCaps;
			if((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT)
				vertexCaps = D3DCREATE_HARDWARE_VERTEXPROCESSING;
			else
				vertexCaps = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

			DirectXErrorCheck(Direct3D->CreateDeviceEx(devID, D3DDEVTYPE_HAL, Window,
				vertexCaps | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
				&d3dParams, NULL, &Direct3DDevice), "Failed to create a new Direct3D device.");

			CudaErrorCheck(cudaD3D9SetDirect3DDevice(Direct3DDevice, devID));

			cudaDeviceProp deviceProp;
			CudaErrorCheck(cudaSetDeviceFlags(cudaDeviceScheduleBlockingSync));
			CudaErrorCheck(cudaSetDevice(devID));
			CudaErrorCheck(cudaGetDeviceProperties(&deviceProp, devID));
		};

		void InternalManager::Cleanup()
		{
			Release(Direct3DDevice);
			DestroyWindow(Window);
			UnregisterClass(TEXT("InternalManager"), NULL);
			cudaDeviceReset();
		};

		int InternalManager::GetDevID()
		{
			return devID;
		};

		HRESULT InternalManager::CheckDeviceState()
		{
			if (Direct3DDevice)
				return Direct3DDevice->CheckDeviceState(NULL);
			else
				return D3DERR_DEVICELOST;
		}

		HRESULT InternalManager::CreateSurface(UINT Width, UINT Height, bool UseAlpha, UINT AntiAliasSamples, IDirect3DSurface9** Direct3DSurface)
		{
			return Direct3DDevice->CreateRenderTarget(Width, Height, UseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
				static_cast<D3DMULTISAMPLE_TYPE>(0), AntiAliasSamples, FALSE, Direct3DSurface, NULL);
		}
	}
}